package org.example.semantic;

import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.eclipse.lsp4j.*;
import org.example.project.Document;
import org.example.project.Project;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class SemanticTokensProvider {
    private final com.neuronbit.parser.MySqlLexer lexer;
    private final CommonTokenStream tokens;
    private final com.neuronbit.parser.MySqlParser parser;
    private final ParseTree parseTree;
    private final List<Integer> data = new ArrayList<>();
    private int prevLine;
    private int prevColumn;

    public static SemanticTokens semanticTokensFull(Project project, SemanticTokensParams params) {
        Document document = project.get(params.getTextDocument().getUri());
        String text = document.getText();

        return new SemanticTokensProvider(text).visit();
    }

    public SemanticTokensProvider(String text) {
        CharStream charStream = CharStreams.fromString(text);
        this.lexer = new com.neuronbit.parser.MySqlLexer(charStream);
        this.tokens = new CommonTokenStream(lexer);
        this.parser = new com.neuronbit.parser.MySqlParser(tokens);
        this.parseTree = parser.root();
        this.prevLine = 1;
        this.prevColumn = 0;
    }

    public SemanticTokens visit() {
        new TokenIdentifyVisitor(this).visit(parseTree);
        return new SemanticTokens(data);
    }

    public void push(TerminalNode node, int tokenType, int modifiers) {
        if (node != null) {
            push(node.getSymbol(), tokenType, modifiers);
        }
    }

    public void push(Token token, int tokenType, int modifiers) {
        int line = token.getLine() - prevLine;
        int column = token.getLine() == prevLine ? token.getCharPositionInLine() - prevColumn : token.getCharPositionInLine();
        int length = token.getText().length();
        prevLine = token.getLine();
        prevColumn = token.getCharPositionInLine();
        data.add(line);
        data.add(column);
        data.add(length);
        data.add(tokenType);
        data.add(modifiers);
    }


    public static final class TokenType {
        public static final int PROPERTY = 0;
        public static final int FUNCTION = 1;
        public static final int KEYWORD = 2;
        public static final int COMMENT = 3;
        public static final int NUMBER = 4;
        public static final int STRING = 5;
        public static final int OPERATOR = 6;
        public static final int VARIABLE = 7;
        public static final int ENUMMEMBER = 8;
    }

    public static final class TokenModifier {
        public static final int NONE = 0;
        public static final int DECLARATION = 1;
        public static final int READONLY = 2;
        public static final int STATIC = 4;
    }

    public static final SemanticTokensLegend SEMANTIC_TOKENS_LEGEND;

    static {
        Map<Integer, String> tokenTypes = new TreeMap<>();
        tokenTypes.put(TokenType.PROPERTY, SemanticTokenTypes.Property);
        tokenTypes.put(TokenType.FUNCTION, SemanticTokenTypes.Function);
        tokenTypes.put(TokenType.KEYWORD, SemanticTokenTypes.Keyword);
        tokenTypes.put(TokenType.COMMENT, SemanticTokenTypes.Comment);
        tokenTypes.put(TokenType.NUMBER, SemanticTokenTypes.Number);
        tokenTypes.put(TokenType.STRING, SemanticTokenTypes.String);
        tokenTypes.put(TokenType.OPERATOR, SemanticTokenTypes.Operator);
        tokenTypes.put(TokenType.VARIABLE, SemanticTokenTypes.Variable);
        tokenTypes.put(TokenType.ENUMMEMBER, SemanticTokenTypes.EnumMember);

        Map<Integer, String> tokenModifiers = new TreeMap<>();
        tokenModifiers.put(TokenModifier.DECLARATION, SemanticTokenModifiers.Declaration);
        tokenModifiers.put(TokenModifier.READONLY, SemanticTokenModifiers.Readonly);
        tokenModifiers.put(TokenModifier.STATIC, SemanticTokenModifiers.Static);

        SEMANTIC_TOKENS_LEGEND = new SemanticTokensLegend(new ArrayList<>(tokenTypes.values()), new ArrayList<>(tokenModifiers.values()));
    }
}
